package com.freestyle.nettychat.client;


import com.freestyle.netty.easynetty.client.GeneralNettyClientFactory;
import com.freestyle.netty.easynetty.client.interfaces.IGeneralClient;
import com.freestyle.netty.easynetty.lock.StampedLockPromiseUtil;
import com.freestyle.nettychat.chat.ChatCommand;
import com.freestyle.nettychat.chat.CodeConsts;
import com.freestyle.nettychat.chat.attachment.AttachmentDecoder;
import com.freestyle.nettychat.chat.attachment.AttachmentEncoder;
import com.freestyle.nettychat.chat.dto.ChatMessage;
import com.freestyle.nettychat.chat.dto.JSONData;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;

import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**客户端应用
 * Created by rocklee on 2022/2/6 10:47
 */
public class Client {
  private static final AttributeKey<ClientRoutine> clientRoutineAttributeKey=AttributeKey.newInstance("clientRoutine");
  private static ClientRoutine getClientRoutine(ChannelHandlerContext ctx){
    Attribute<ClientRoutine> clientRoutineAttribute=ctx.channel().attr(clientRoutineAttributeKey);
    return clientRoutineAttribute.get();
  }

  public static void newClient(Consumer<ClientRoutine> consumer) throws InterruptedException {
    IGeneralClient client = new GeneralNettyClientFactory().getClient("localhost", 9900);
    try {
      final ClientRoutine clientRoutine=new ClientRoutine(new StampedLockPromiseUtil<JSONData>());
        client.run(false, ch -> {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024*1024*20, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4))
                .addLast(CodeConsts.createEncoders())
                .addLast(new AttachmentEncoder(CodeConsts.int2Bytes(102)))
                .addLast(new AttachmentDecoder(CodeConsts.int2Bytes(102)))
                .addLast(CodeConsts.createMultiDecoder())
                .addLast(new SimpleChannelInboundHandler<Object>(false) {
                  @Override
                  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws
                          Exception {
                    System.out.println(cause.getMessage());
                    ctx.close();
                  }
                  @Override
                  protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                    final ClientRoutine clientRoutine=getClientRoutine(ctx);
                    try {
                      if (msg instanceof ChatMessage) {
                        //System.out.println("Client<<<:"+msg.toString());
                        ChatMessage chatMessage = (ChatMessage) msg;
                        clientRoutine.proc(chatMessage);
                      } else if (msg instanceof JSONData) {
                        clientRoutine.getPromiseUtil().signal(((JSONData) msg).getId(), (JSONData) msg);
                        clientRoutine.showResult((JSONData) msg);
                      }
                    }
                    finally {
                      ctx.channel().eventLoop().schedule(()->{
                        clientRoutine.showPrompt();
                      },100, TimeUnit.MILLISECONDS);
                    }
                  }

                  @Override
                  public void channelActive(ChannelHandlerContext ctx) throws Exception {
                    super.channelActive(ctx);
                    Attribute<ClientRoutine> clientRoutineAttribute=ctx.channel().attr(clientRoutineAttributeKey);
                    ClientRoutine c=clientRoutineAttribute.get();
                    if (c==null){
                      c=clientRoutine;
                      clientRoutineAttribute.set(c);
                    }
                    clientRoutine.setContext(ctx);
                    System.out.println("Connected:"+ctx.channel().remoteAddress());
                  }
                  @Override
                  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                    super.channelInactive(ctx);
                    Attribute<ClientRoutine> clientRoutineAttribute=ctx.channel().attr(clientRoutineAttributeKey);
                    clientRoutineAttribute.set(null);
                    System.out.println("DisConnected:"+ctx.channel().remoteAddress());
                  }
                });

      });
      clientRoutine.setClient(client);
      consumer.accept(clientRoutine);
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      client.close();
    }
  }
  public static void main(String[] args) {
    CompletableFuture task=CompletableFuture.runAsync(()->{
      try {
        newClient(ch->doMainRoutine(ch));
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
    task.join();
  }

  public static boolean procCommand(ClientRoutine clientRoutine,String cmd){
    String command=cmd.split(" ")[0];

    if (command.equalsIgnoreCase(ChatCommand.createRoom.getValue())) {
      JSONData ret = clientRoutine.sendCommand(cmd, true);
      if (ret.getErrCode() == 0) {
        String tmpRoom = cmd.split(" ")[1];
        ret = clientRoutine.sendCommand(ChatCommand.enterRoom.getValue() + " " + tmpRoom, true);
        if (ret.getErrCode() == 0) {
          clientRoutine.setRoom( tmpRoom);
          return true;
        }
        return false;
      }
      return false;
    } else if (command.equalsIgnoreCase(ChatCommand.enterRoom.getValue())) {
      String tmpRoom = cmd.split(" ")[1];
      JSONData ret = clientRoutine.sendCommand(ChatCommand.enterRoom.getValue() + " " + tmpRoom, true);
      if (ret.getErrCode() == 0) {
        clientRoutine.setRoom(tmpRoom);
        return true;
      }
      return false;
    }
    else if (command.equalsIgnoreCase(ChatCommand.login.getValue())){
      JSONData ret = clientRoutine.sendCommand(cmd, true);
      if (ret.getErrCode() == 0) {
        clientRoutine.setUid(cmd.split(" ")[1]);
      }
      return ret.getErrCode()==0;
    }
    else if (command.equalsIgnoreCase(ChatCommand.removeChatRoom.getValue())){
      JSONData ret = clientRoutine.sendCommand(cmd, true);
      if (ret.getErrCode() == 0) {
        clientRoutine.setRoom(null);
      }
      return ret.getErrCode()==0;

    }
    else if (command.equalsIgnoreCase(ChatCommand.logout.getValue())) {
      JSONData ret = clientRoutine.sendCommand(cmd, true);
      if (ret.getErrCode() == 0) {
        clientRoutine.setUid(null);
      }
      return ret.getErrCode()==0;
    }
    else if (command.equalsIgnoreCase(ChatCommand.exit.getValue())){
      if (clientRoutine.getRoom()==null){
        return true;
      }
      clientRoutine.exitRoom(0);
      clientRoutine.setRoom(null);
      return true;
    }
    else if (command.equalsIgnoreCase(ChatCommand.sendFile.getValue())){
      clientRoutine.sendFile(cmd.split(" ")[1]);
    }
    else if (command.equalsIgnoreCase(ChatCommand.fetchFile.getValue())){
      clientRoutine.sendCommand(cmd);
    }
    else{
      clientRoutine.sendCommand(cmd);
    }
    return false;

  }
  private static void doMainRoutine(ClientRoutine clientRoutine){
    clientRoutine.showHelp();

    Scanner sc = new Scanner(System.in);
    clientRoutine.showPrompt();
    while (true) {
      try {
        if (clientRoutine.getUid()== null) {
          System.out.println("Please login first");
          clientRoutine.showPrompt();
        }
        if (clientRoutine.getRoom() == null || clientRoutine.getUid() == null) {
          String cmd = sc.nextLine().replaceAll("\\s{1,}", " ");
          procCommand(clientRoutine,cmd) ;
          if (cmd.equalsIgnoreCase(ChatCommand.exit.getValue()))break;
        } else {
          String msg = sc.nextLine();
          if (msg.startsWith("!")) {
            String cmd=msg.replaceAll("\\s{1,}", " ").substring(1);
            procCommand(clientRoutine,cmd);
          }
          else{
            clientRoutine.sendMessage(msg);
          }
        }
        Thread.sleep(200);
      }
      catch (Exception e){
        clientRoutine.output(e.getMessage());
        e.printStackTrace();
      }
    }

  }
}
